package com.nfha.algorithm.geo.navigation;

import java.io.Serializable;
import java.util.Deque;
import java.util.HashSet;
import java.util.stream.Stream;

/**
 * Map类存储地图信息
 * 
 * @author Hao
 *
 */
public class GeoMap implements Serializable{
	/**
	 * 
	 */
	private static final long serialVersionUID = 1L;
	// 节点尺寸
	private double nodeSize;
	// 横向节点数
	private int xScale;
	// 纵向节点数
	private int yScale;
	//
	private double startX;
	private double startY;
	// 存储节点的二维数组
	private GeoMapNode[][] geoMapNodes;

	public double[][] polygonGeoPoints;
	
	double max_lon , max_lat , min_lon , min_lat ,step_lon,step_lat;
	
	
	public GeoMap(int xScale, int yScale, double nodeSize, double startX, double startY) {
		geoMapNodes = new GeoMapNode[yScale][xScale];
		this.nodeSize = nodeSize;
		this.xScale = xScale;
		this.yScale = yScale;
		this.startX = startX;
		this.startY = startY;
		double y = startY;
		for (int i = 0; i < yScale; i++) {
			double x = startX;
			for (int j = 0; j < xScale; j++) {
				geoMapNodes[i][j] = new GeoMapNode(x, y);
				x += nodeSize;
			}
			y += nodeSize;
		}
	}

	public void print() {
		for (int i = 0; i < yScale; i++) {
			for (int j = 0; j < xScale; j++) {
				if (geoMapNodes[i][j].isReachable())
					System.out.print("□");
				else
					System.out.print("■");
			}
			System.out.println();
		}
	}

	public void print(Deque<GeoMapNode> result) {
		for (int i = 0; i < yScale; i++) {
			for (int j = 0; j < xScale; j++) {
				if (result.contains(geoMapNodes[i][j]))
					System.out.print("*");
				else if (geoMapNodes[i][j].isReachable())
					System.out.print("□");
				else
					System.out.print("■");
			}
			System.out.println();
		}
	}

	/**
	 * 编辑地图中的障碍
	 * 
	 * @param 障碍中心x位置
	 * @param 障碍中心y位置
	 * @param size半径
	 */
	public void editObstacle(double x, double y, double size) {
		int unitSize = (int) (size / nodeSize);
		GeoMapNode obs = XY2GeoMapNode(x, y);
		if (obs == null)
			return;
		HashSet<GeoMapNode> obstacles = new HashSet<GeoMapNode>();
		obstacles.add(obs);
		for (int i = 0; i < unitSize - 1; i++) {
			HashSet<GeoMapNode> temp = new HashSet<GeoMapNode>(obstacles);
			for (GeoMapNode node : temp) {
				obstacles.addAll(getNeighbors(node));
			}
		}
		for (GeoMapNode node : obstacles) {
			node.setReachable(false);
		}
	}

	/**
	 * 获得x y位置的节点
	 * 
	 * @param x
	 * @param y
	 * @return
	 */
	public GeoMapNode XY2GeoMapNode(double x, double y) {
		int j = (int) ((x - startX) / nodeSize);
		int i = (int) ((y - startY) / nodeSize);
		if (j < xScale && i < yScale && j >= 0 && i >= 0)
			return geoMapNodes[i][j];
		else
			return null;
	}

	/**
	 * 返回给定节点在map中对应的节点
	 * 
	 * @param node
	 * @return
	 */
	public GeoMapNode node2GeoMapNode(GeoMapNode node) {
		return XY2GeoMapNode(node.getX(), node.getY());
	}

	/**
	 * 获得数组中的索引
	 * 
	 * @param node
	 * @return
	 */
	public int getXIndex(GeoMapNode node) {
		return (int) ((node.getX() - startX) / nodeSize);
	}

	public int getYIndex(GeoMapNode node) {
		return (int) ((node.getY() - startY) / nodeSize);
	}

	/**
	 * 返回邻居节点
	 * 
	 * @param node
	 * @return
	 */
	public HashSet<GeoMapNode> getNeighbors(GeoMapNode node) {
		HashSet<GeoMapNode> neighbors = new HashSet<GeoMapNode>();
		int x = getXIndex(node);
		int y = getYIndex(node);
		for (int i = -1; i <= 1; i++) {
			if (y + i >= yScale || y + i < 0)
				continue;
			for (int j = -1; j <= 1; j++) {
				if (i == 0 && j == 0)
					continue;
				if (x + j >= xScale || x + j < 0)
					continue;
				if (!geoMapNodes[y + i][x + j].isReachable())
					continue;
				if ((i * j == -1 || i * j == 1)
						&& ((!geoMapNodes[y + i][x].isReachable()) || (!geoMapNodes[y][x + j].isReachable())))
					continue;
				neighbors.add(geoMapNodes[y + i][x + j]);
			}
		}
		return neighbors;
	}

	public  GeoMap (int xScale, int yScale, double nodeSize, double startX, double startY,double[][] polygonGeoPoints) {

		max_lon = polygonGeoPoints[0][0]; 
		max_lat = polygonGeoPoints[0][1]; 
		min_lon = max_lon;
		min_lat = max_lat;

		this.polygonGeoPoints=polygonGeoPoints;
		
		for (double[] polygonGeoPoint : polygonGeoPoints) {

			double temp_lon = polygonGeoPoint[0], temp_lat = polygonGeoPoint[1];

			if (temp_lon > max_lon)
				max_lon = temp_lon;
			
			if (temp_lon < min_lon)
				min_lon = temp_lon;

			if (temp_lat > max_lat)
				max_lat = temp_lat;

		    if (temp_lat < min_lat)
				min_lat = temp_lat;

		}

		step_lon=((max_lon-min_lon)/xScale);
		
		step_lat=(max_lat-min_lat)/yScale;
		
		System.out.println(max_lon + "," + max_lat + "," + min_lon + "," + min_lat);
		
		System.out.println(step_lon + "," + step_lat );
		
		
		geoMapNodes = new GeoMapNode[yScale][xScale];
		this.nodeSize = nodeSize;
		this.xScale = xScale;
		this.yScale = yScale;
		this.startX = startX;
		this.startY = startY;
		double y = startY;
		for (int i = 0; i < yScale; i++) {
			double x = startX;
			for (int j = 0; j < xScale; j++) {
				GeoMapNode gmn=new GeoMapNode(x, y);
				gmn.lat=(i*step_lat)+min_lat;
				gmn.lon=(j*step_lon)+min_lon;
				geoMapNodes[i][j] = gmn;
				
				
				for (double[] polygonGeoPoint : polygonGeoPoints) {
					
					boolean isIn=polygonGeoPoint[0]<=gmn.lon+step_lon&&polygonGeoPoint[0]>=gmn.lon-step_lon;
					isIn=isIn&&polygonGeoPoint[1]<=gmn.lat+step_lat&&polygonGeoPoint[1]>=gmn.lat-step_lat;
					if(isIn) {
					  editObstacle(x, y, 1);
					  break;
					}
				}
				
				x += nodeSize;
			}
			y += nodeSize;
		}
		
		
		
		

		
	
	}
	
	
	public GeoMapNode getMapNodeByIndex(int x,int y) {
		GeoMapNode ret=geoMapNodes[y][x];
		return ret;
	}
	
	public GeoMapNode getMapNodeByGeo(double lon,double lat) {
		GeoMapNode ret=null;
		for(GeoMapNode[] row : geoMapNodes) {
			for(GeoMapNode gmn :row) {
				boolean isIn=lon<=gmn.lon+step_lon&&lon>=gmn.lon-step_lon;
				isIn=isIn&&lat<=gmn.lat+step_lat&&lat>=gmn.lat-step_lat;
				if(isIn) {
				  ret=gmn;
				  break;
				}
			}
		}
		return ret;
	}

}